﻿using FySystem.Extension;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FySystem.Net.IOT.CommandSender
{
    /* 使用方式：
     * 1.假设需求每5分钟请求一次数据，先初始化发送管理器，BSWeatherProtocol.CreateCommands
     * 为每5分钟创建指令的函数，返回一个数组，因此每次可以创建多条指令，其中的BSWeatherReal
     * 为设备的实时数据实体：
     * SenderManager manager = new SenderManager<BSWeatherReal>(300000, BSWeatherProtocol.CreateCommands);
     * 初始化完成后管理器每隔300000ms定时调用一次BSWeatherProtocol.CreateCommands函数，并将返回的
     * 指令进行发送，可以设置超时时间和重发次数，默认10s超时，发送3次。
     * 2.如果除了定时发送外还要给设备发送数据，调用SenderManager.Send()方法，不要直接用socket.Send()，
     * 否则可能会有问题，且不会失败重发。
     * 3.很多硬件设备两条数据发送时间间隔不能小于指定时间，否则只能识别第一条数据，可以用过
     * SenderManager.SplitTime属性来设置，默认是100ms。
     * 4.大多数发送的数据都会回应一条消息，在收到消息后需要手动对数据进行校验，如果确实是之前发送的数据
     * 的回复，则必须调用DeviceR.SetCurrentCommandComplete()来通知库向设备发送的指令已经收到回复，否则
     * 该库会认为没收到回复从而在10s超时后进行重发。
     * */
    public class SenderManager<T>
    {
        private SendUtils<T> _sendUtils = new SendUtils<T>();

        /// <summary>
        /// 所有的设备连接
        /// </summary>
        public ConcurrentDictionary<string, DeviceR<T>> DeviceRs = new ConcurrentDictionary<string, DeviceR<T>>();

        /// <summary>
        /// 定时发送间隔，默认1分钟，最小不能小于10ms
        /// </summary>
        public int SendInterval { get; set; } = 60000;

        /// <summary>
        /// 同一个连接的两条数据之间的间隔
        /// </summary>
        public int SplitTime { get; set; } = 100;

        /// <summary>
        /// 超时时间，默认10s，单位ms
        /// </summary>
        public int Timeout { get; set; } = 10000;

        /// <summary>
        /// 重发次数，默认3次
        /// </summary>
        public int Repeat { get; set; } = 3;

        /// <summary>
        /// 指令创建委托
        /// </summary>
        public Func<DeviceR<T>, List<byte[]>> CommandFunc { get; set; }

        /// <summary>
        /// 设置定时发送间隔，单位毫秒
        /// </summary>
        /// <param name="interval"></param>
        /// <param name="commandFunc">创建指令的委托</param>
        public SenderManager(int interval, Func<DeviceR<T>, List<byte[]>> commandFunc)
        {
            try
            {
                if (interval <= 0)
                    throw new Exception("定时间隔不能小于0~");
                if (commandFunc == null)
                    throw new Exception("传入指令创建委托为空~");

                SendInterval = interval;
                CommandFunc = commandFunc;

                //创建定时任务
                Task task = new Task(Pool);
                task.Start();

                //垃圾回收定时任务
                Task taskGC = new Task(GCDeviceR);
                taskGC.Start();
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 添加设备映射，只有添加之后才会定时给设备发送指令
        /// </summary>
        /// <param name="device"></param>
        /// <exception cref="NotImplementedException"></exception>
        internal void AddDeviceR(DeviceR<T> device)
        {
            try
            {
                if (device != null)
                {
                    if (!string.IsNullOrWhiteSpace(device.GatewaySN))
                    {
                        DeviceRs.AddOrUpdate(device.GatewaySN, device,
                            (key, value) => device);
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 定时器
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        private void Pool()
        {
            while (true)
            {
                Thread.Sleep(10);
                try
                {
                    if (DeviceRs.Count == 0)
                        continue;

                    DateTime now = DateTime.Now;

                    foreach (var item in DeviceRs.Values)
                    {
                        bool needToSend = false;

                        if (item.LastSendTime == null)
                        {
                            needToSend = true;
                        }
                        else if ((now - item.LastSendTime.Value).TotalMilliseconds >= SendInterval)
                        {
                            needToSend = true;
                        }

                        if (needToSend == true)
                        {
                            //创建要发送的指令列表
                            List<byte[]> commands = CommandFunc?.Invoke(item);
                            if (commands.IsNullOrEmpty() == false)
                            {
                                //更新指令开始发送时间
                                item.LastSendTime = now;

                                //放入发送队列
                                foreach (var cmd in commands)
                                    Send(item, cmd);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    FyLogContext.Write(ex.Message);
                }
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="deviceR"></param>
        /// <param name="data"></param>
        public void Send(DeviceR<T> deviceR, byte[] data, bool needReply = true)
        {
            try
            {
                if (deviceR == null)
                    throw new Exception("传入设备映射为空~");
                if (data == null)
                    throw new Exception("需要发送的数据为空~");

                CmdInfo<T> cmdInfo = new CmdInfo<T>();
                cmdInfo.DeviceR = deviceR;
                cmdInfo.Command = data;
                cmdInfo.Repeat = Repeat;
                cmdInfo.SplitTime = SplitTime;
                cmdInfo.Timeout = Timeout;
                cmdInfo.NeedReply = needReply;
                //将下一条指令记为新创建的指令，这样在队列循环的时候才能按顺序发送
                if (deviceR.LastEnqueueCommand != null)
                    deviceR.LastEnqueueCommand.NextCommand = cmdInfo;
                deviceR.LastEnqueueCommand = cmdInfo;
                _sendUtils.SendQueue.Enqueue(cmdInfo);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 定时删除失效的设备映射
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        private void GCDeviceR()
        {
            while (true)
            {
                Thread.Sleep(60000);
                try
                {
                    if (DeviceRs.Count <= 0)
                        continue;

                    DateTime now = DateTime.Now;
                    List<string> gatewaysns = new List<string>();
                    foreach (var item in DeviceRs.Values)
                    {
                        if (item.LastSendTime == null)
                            continue;
                        if ((now - item.LastSendTime.Value).TotalHours >= 1)
                            gatewaysns.Add(item.GatewaySN);
                    }

                    if (gatewaysns.Count > 0)
                    {
                        foreach (var key in gatewaysns)
                        {
                            DeviceR<T> d = null;
                            DeviceRs.Remove(key, out d);
                        }
                    }
                }
                catch (Exception ex)
                {
                    FyLogContext.Write(ex.Message);
                }
            }
        }

        /// <summary>
        /// 获取设备
        /// </summary>
        /// <param name="gatewaySN"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        internal DeviceR<T> GetDeviceR(string gatewaySN)
        {
            try
            {
                DeviceR<T> res = null;
                DeviceRs.TryGetValue(gatewaySN, out res);
                return res;
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}
